

#include <pci.h>
#include <string.h>
#include <debug.h>
#include <gdt.h>
#include <io.h>

static int check_for_pci_bios_service(pci_bios32_sd_t *sd, bios32_indirect_t *interface);
static int check_for_pci_bios_interface(bios32_indirect_t *interface, pci_bios_interface_t *s);

int init_pci(void)
{
	int ret;
	
	pci_bios32_sd_t *p;
	
	p = find_pci_bios32((void*)PCI_BIOS32_START, (void*)PCI_BIOS32_END);
	if (p == NULL)
	{
		debugf("[PCI] BIOS32 Service Directory Not Found!\n");
		ret = ERR_PCI_BIOS32_NOTFOUND;
	}
	else
	{
		debugf("[PCI] BIOS32 Service Directory v%d @ 0x%08x\n", p->revision, p);
		debugf("[PCI] BIOS32 Entry Point @ 0x%08x\n", p->entry_ptr);
		
		bios32_indirect_t interface;
		ret = check_for_pci_bios_service(p, &interface);
		
		if (ret == 0) 
		{
			debugf("[PCI] BIOS32 Service @ 0x%08x\n", interface.addr);
			
			pci_bios_interface_t s;
			ret = check_for_pci_bios_interface(&interface, &s);
			
			if (ret == 0)
			{
				debugf("[PCI] BIOS Interface v%x.%x\n", s.version_major, s.version_minor);
				debugf("[PCI] %d Busses Reported\n", s.last_bus+1);
				
				if (s.mechanism == 1)
				{
					int bus;
					for (bus = 0; bus <= s.last_bus; bus++)
						pci_scan_bus(bus);
				}
				else 
				{
					debugf("[PCI] Configuration Mechanism 0x%x Not Supported!\n", s.mechanism);
					ret = ERR_PCI_BAD_MECH;
				}
			}
			else
			{
				debugf("[PCI] BIOS Interface Not Implemented\n");
				ret = ERR_PCI_BIOS_NOTIMPL;
			}
		}
		else
		{
			debugf("[PCI] BIOS32 Service Not Implemented!\n");
			ret = ERR_PCI_BIOS32_NOTIMPL;
		}
	}
	
	return ret;
}

dword pci_read_dword(dword bus, dword dev, dword func, dword reg)
{
	dword config;
	dword val = 0;
	
	config = PCI_CONFIG_ENABLE | ((bus & 0xFF) << 16) | ((dev & 0x1F) << 11) |
	 ((func & 0x07) << 8) | (reg & 0xFC);
	 
	 outl(PCI_CONFIG_ADDR, config);
	 
	 val = inl(PCI_CONFIG_DATA);
	 
	 return val;
}

void pci_write_dword(dword bus, dword dev, dword func, dword reg, dword val)
{
	dword config;
	
	config = PCI_CONFIG_ENABLE | ((bus & 0xFF) << 16) | ((dev & 0x1F) << 11) |
	 ((func & 0x07) << 8) | (reg & 0xFC);
	 
	 outl(PCI_CONFIG_ADDR, config);
	 
	 outl(PCI_CONFIG_DATA, val);
}

pci_bios32_sd_t * find_pci_bios32(void *start, void *end)
{
	void * addr;
	pci_bios32_sd_t *sd = NULL;
	unsigned char checksum, i;
	unsigned char *ptr;
	
	for (addr = start; addr <= end; addr+=4)
	{		
		sd = (pci_bios32_sd_t*)addr;
		
		if (strncmp(sd->signature, PCI_BIOS32_SIGNATURE, 4) == 0)
		//if (sd->signature == ((('_' << 0) + ('3' << 8) + ('2' << 16) + ('_' << 24))))
		{			
			ptr = (unsigned char*)addr;
			checksum = 0;
			for (i = 0; i < sizeof(pci_bios32_sd_t); i++) 
				checksum += *(ptr++);
			
			if ((checksum == 0) && (sd->length == 1))
				return addr;
		}
	}
	
	return NULL;
}

int check_for_pci_bios_service(pci_bios32_sd_t *sd, bios32_indirect_t *interface)
{
	dword base = 0, length = 0, offset = 0;
	byte ret = -1;
	
	bios32_indirect_t ind;
	ind.segment = SEL_CODE;
	ind.addr = sd->entry_ptr;
	
	asm volatile("lcall *(%6)" : "=a" (ret), "=b" (base), "=c" (length), "=d" (offset)
	: "0" (PCI_BIOS_SERVICE_ID), "1" (0), "D" (&ind));
	
	//debugf("[PCI] ret: %x, base: %x, length: %x, offset: %x\n", ret, base, length, offset);
	
	if (ret == 0) {
		interface->segment = SEL_CODE;
		interface->addr = base + offset;
	}
	
	return (int)ret;
}

int check_for_pci_bios_interface(bios32_indirect_t *interface, pci_bios_interface_t *s)
{
	//dword base = 0, length = 0, offset = 0;
	dword edx, flags;
	word ax, bx;
	byte cl;
	int ret = -1;
	
	asm volatile("lcall *(%6) ; pushf ; popl %%edi" : "=a" (ax), "=b" (bx), "=c" (cl), "=d" (edx), "=D" (flags)
	: "0" (PCI_IF_PCI_FUNC_ID | PCI_IF_BIOS_PRESENT), "D" (interface));
	
	if ((strncmp((char*)&edx, PCI_IF_SIGNATURE, 4) == 0) && ((ax >> 8) == 0) && ((flags & 0x0001) == 0))
	{
		ret = 0;
		s->version_major = (bx >> 8);
		s->version_minor = (bx & 0x00FF);
		s->last_bus = cl;
		s->mechanism = (ax & 0x000F);
	}
		 
	return ret;
}

int pci_scan_bus(dword bus)
{
	dword dev, func;
	dword temp;
	int ret = 0;
	
	for (dev = 0; dev < 32; dev++)
	{
		// check for multifunction
		temp = pci_read_dword(bus, dev, 0, PCI_SPACE_TYPE);
		temp = (temp >> 23) & 0x01;
		
		if (temp == 1)
		{
			// multi-function
			for (func = 0; func < 8; func++)
			{
				pci_scan_device(bus, dev, func);
			}
		}
		else
		{
			// single-function
			pci_scan_device(bus, dev, 0);
		}
	}
	
	return ret;
}

int pci_scan_device(dword bus, dword dev, dword func)
{
	int ret = 0;
	dword type, deviceid, vendorid;
	
	type = pci_read_dword(bus, dev, func, PCI_SPACE_DEVICEID);
	vendorid = type & 0xFFFF;
	
	if (vendorid != 0xFFFF)
	{
		dword base, subclass, interface, revision;
		dword intr;
		deviceid = (type >> 16) & 0xFFFF;
		type = pci_read_dword(bus, dev, func, PCI_SPACE_CLASSREV);
		intr = pci_read_dword(bus, dev, func, PCI_SPACE_INT);
		
		base = (type >> 24) & 0xFF;
		subclass = (type >> 16) & 0xFF;
		interface = (type >> 8) & 0xFF;
		revision = type & 0xFF;
		
		debugf("[PCI] %d:%d:%d %04x %04x %02x %02x %02x %02x (%d)\n", bus, dev, func,
		 vendorid, deviceid, base, subclass, interface, revision, (intr >> 8) & 0xFF);
	}
	else
	{
		// invalid
		ret = ERR_PCI_DEV_INVALID;
	}
	
	return ret;
}
